﻿#region Using Directive

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;

#endregion

namespace ParticleTracker
{
    public class ParticleFrame
    {
        public enum ThresholdMethod
        {
            None,
            Static,
            Adaptive
        }

        int threshold;
        ThresholdMethod thresholdMethod;
        bool thresheld, monochrome;
        Rectangle lockedArea;
        Bitmap origImage;
        Color[,] pixelQuery;
        public List<Piece> Pieces = new List<Piece>();
        public Dictionary<string, Piece> PieceMap = new Dictionary<string, Piece>();

        public ParticleFrame(string filename)
        {
            this.origImage = new Bitmap(filename, false);
            thresholdMethod = ThresholdMethod.None;
            thresheld = true;
            monochrome = false;
        }
        public Rectangle RectangleFull
        {
            get
            {
                //return new Rectangle(0, 0, origImage.Width, origImage.Height);
                return new Rectangle(new Point(0, 0), new Size(origImage.Width, origImage.Height));
            }
        }
        public Rectangle Area
        {
            get
            {
                return lockedArea;
            }
            set
            {
                lockedArea = value;
                this.pixelQuery = null;
            }
        }
        public Bitmap OriginalImage
        {
            get
            {
                return this.origImage;
            }
            set
            {
                this.origImage = value;
            }
        }
        public Bitmap ThresheldImage
        {
            get
            {
                return null;
            }
        }
        public ThresholdMethod Thresholding
        {
            get
            {
                return this.thresholdMethod;
            }
            set
            {
                if (this.thresholdMethod != value)
                {
                    this.thresholdMethod = value;
                    this.thresheld = false;
                }
            }
        }
        public int Threshold
        {
            get
            {
                if (this.thresheld)
                {
                    return this.threshold;
                }
                else
                {
                    this.thresheld = true;
                    switch (this.thresholdMethod)
                    {
                        case ThresholdMethod.Adaptive: return this.AdaptiveThreshold();
                        case ThresholdMethod.Static: return this.StaticThreshold();
                        default:
                        case ThresholdMethod.None: return 0;
                    }
                }
            }
            set
            {
                this.threshold = value;
                this.thresheld = false;
            }
        }
        public int AdaptiveThreshold()
        {
            if (!this.monochrome) this.Monochrome();
            /**
             * 1. An initial threshold (T) is chosen, this can be done randomly or according to any other method desired.
             * 2. The image is segmented into object and background pixels as described above, creating two sets:
             *     1. G1 = {f(m,n):f(m,n)\gtT} (object pixels)
             *     2. G2 = {f(m,n):f(m,n)\leT} (background pixels) 
             *        (note, f(m,n) is the value of the pixel located in the mth column, nth row)
             * 3. The average of each set is computed.
             *     1. m1 = average value of G1
             *     2. m2 = average value of G2
             * 4. A new threshold is created that is the average of m1 and m2
             *     1. T’ = (m1 + m2)/2
             * 5. Go back to step two, now using the new threshold computed in step four, 
             * keep repeating until the new threshold matches the one before it 
             * (i.e. until convergence has been reached).
             * 
             **/
            int Tprime = 122;
            if (this.pixelQuery == null) this.get();
            do
            {
                this.threshold = Tprime;
                List<Color> G1 = new List<Color>();
                List<Color> G2 = new List<Color>();
                for (int x = 0; x < this.lockedArea.Width; ++x)
                {
                    for (int y = 0; y < this.lockedArea.Height; ++y)
                    {
                        Color p = pixelQuery[x, y];
                        if (p.R > this.threshold)
                            G1.Add(p);
                        else
                            G2.Add(p);
                    }
                }
                double m1 = 0, m2 = 0;
                foreach (Color p in G1)
                {
                    m1 += p.R;
                }
                foreach (Color p in G2)
                {
                    m2 += p.R;
                }
                if (G1.Count > 0)
                    m1 /= G1.Count;
                if (G2.Count > 0)
                    m2 /= G2.Count;
                Tprime = (int)((m1 + m2) / 2);
            } while (this.threshold != Tprime);
            return this.StaticThreshold();
        }
        public int StaticThreshold()
        {
            if (!this.monochrome) this.Monochrome();
            for (int x = 0; x < this.lockedArea.Width; ++x)
            {
                for (int y = 0; y < this.lockedArea.Height; ++y)
                {
                    Color p = pixelQuery[x, y];
                    Color q;
                    if (p.R > this.threshold)
                    {
                        q = Color.FromArgb(255, 255, 255);
                    }
                    else q = Color.FromArgb(0, 0, 0);
                    pixelQuery[x, y] = q;
                }
            }
            this.put(pixelQuery);
            this.thresheld = true;
            return this.threshold;
        }
        public class Pixel
        {
            public const int MaxValue = 255;
            public const int MinValue = 0;
            int[] vals;
            public Pixel(int blue, int green, int red)
            {
                vals = new int[3];
                vals[0] = blue;
                vals[1] = green;
                vals[2] = red;
            }
            public int Blue
            {
                get
                {
                    return vals[0];
                }
                set
                {
                    vals[0] = value;
                }
            }
            public int Green
            {
                get
                {
                    return vals[1];
                }
                set
                {
                    vals[1] = value;
                }
            }
            public int Red
            {
                get
                {
                    return vals[2];
                }
                set
                {
                    vals[2] = value;
                }
            }
        }
        public Color get(Point p)
        {
            if (this.pixelQuery == null) this.get();
            return pixelQuery[p.X, p.Y];
        }


        public Color[,] get()
        {
            pixelQuery = new Color[lockedArea.Width, lockedArea.Height];
            for (
                int y = lockedArea.Y;
                y < (lockedArea.Y + lockedArea.Height) && y < origImage.Height;
                ++y
                )
            {
                for (
                    int x = lockedArea.X;
                    x < (lockedArea.X + lockedArea.Width) && x < origImage.Width;
                    ++x
                    )
                {
                    pixelQuery[x - lockedArea.X, y - lockedArea.Y] = origImage.GetPixel(x, y);
                }
            }
            return pixelQuery;
        }

        public ParticleFrame put(Color[,] pixels)
        {
            for (
                int y = lockedArea.Y;
                y < (lockedArea.Y + lockedArea.Height) && y < origImage.Height;
                ++y
                )
            {
                for (
                    int x = lockedArea.X;
                    x < (lockedArea.X + lockedArea.Width) && x < origImage.Width;
                    ++x
                    )
                {
                    origImage.SetPixel(x, y, pixels[x - lockedArea.X, y - lockedArea.Y]);
                }
            }
            return this;
        }
        public ParticleFrame put(int x, int y, Color pixel)
        {
            origImage.SetPixel(x, y, pixel);
            return this;
        }

        public void Monochrome()
        {
            if (this.pixelQuery == null) this.get();
            for (int x = 0; x < this.lockedArea.Width; ++x)
            {
                for (int y = 0; y < this.lockedArea.Height; ++y)
                {
                    Color p = pixelQuery[x, y];
                    int grey = (int)(.299 * p.R + .587 * p.G + .114 * p.B);
                    pixelQuery[x, y] = Color.FromArgb(grey, grey, grey);
                }
            }
            this.put(pixelQuery);
            this.monochrome = true;
        }
    } // End of Class
}
